/**
 * \file  combinator.cpp
 * \brief Definition of Combinator implementation class.
 *
 * LAV: Lib Anti Virals
 * <http://lav.googlecode.com/>
 * Copyright (C) 2013 Boretto Martin, Lenarduzzi Federico, FuDePAN
 *
 * This file is part of the LAV project.
 *
 * Contents:       Cpp file for LAV providing class Combinator implementation.
 *
 * System:         LAV
 * Language:       C++
 *
 * Author:         Boretto Martin
 * E-Mail:         martinboretto@gmail.com
 *
 * Author:         Lenarduzzi Federico
 * E-Mail:         federicolenarduzzi@gmail.com
 *
 * Author:         Alberione Fernando
 * E-Mail:         falberione037@gmail.com
 *
 * Author:         Ocampo Emanuel
 * E-Mail:         eocampo86@gmail.com
 *
 * LAV is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LAV is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LAV.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "lav/antiviral_set.h"

namespace lav
{

// -----------------------
// Public methods section
// -----------------------


void AntiviralSet::Combinator::pre_processing(const biopp::PseudoNucSequence& sequence) const
{
    ArvsSet::iterator it_av;
    for (it_av = antivirals.begin(); it_av != antivirals.end(); ++it_av)
        update_arv_information(sequence, it_av);
}


void AntiviralSet::Combinator::generate_power_set(ResisBasedAntiviral::CombinationContainer& power_set_group_generated) const
{
    ArvsSet::const_iterator it_av;
    for (it_av = antivirals.begin(); it_av != antivirals.end(); ++it_av)
        make_power_set(it_av, power_set_group_generated);
}


void AntiviralSet::Combinator::generate_cartesian_product(const ResisBasedAntiviral::CombinationContainer& antivirals_minimazed,
        ResisBasedAntiviral::CombinationContainer& antivirals_x) const
{
    ResisBasedAntiviral::CombinationContainer aux_antivirals_x;
    cartesian_product(antivirals_minimazed, aux_antivirals_x);
    split_antivirals_x(aux_antivirals_x, antivirals_x);
}


void AntiviralSet::Combinator::expand_to_nucleotids(const biopp::PseudoNucSequence& sequence,
        const ResisBasedAntiviral::CombinationContainer& antivirals_x,
        ARVExpansionContainer& antivirals_expanded) const
{
    ResisBasedAntiviral::CombinationContainer::const_iterator it_comb_cont;
    for (it_comb_cont = antivirals_x.begin(); it_comb_cont != antivirals_x.end(); ++it_comb_cont)
    {
        ARVExpansion current_second;
        ARVExpansion second_expansion;

        // Generate a list of pairs ((pos, triplet), ...) and make cartesian product
        process_combinations_container(it_comb_cont, sequence, current_second);
        // Now, the combination's expansion is ready to be inserted
        mili::insert_into(antivirals_expanded, current_second);
    }

}


void AntiviralSet::Combinator::generate_triplets_cartesian_product(const ARVExpansionContainer& antivirals_expanded,
        ARVExpansionContainer& antivirals_expanded_with_cp) const
{
    ARVExpansionContainer::const_iterator it_expanded;
    for (it_expanded = antivirals_expanded.begin(); it_expanded != antivirals_expanded.end(); ++it_expanded)
    {
        ARVExpansion current_second = (*it_expanded);
        ARVExpansion current_second_with_cp;
        cartesian_product(current_second, current_second_with_cp);
        mili::insert_into(antivirals_expanded_with_cp, current_second_with_cp);
    }
}


void AntiviralSet::Combinator::combine_antivirals(ARVExpansionContainer& combination) const
{
    ResisBasedAntiviral::CombinationContainer power_set_combination, cartesian_product_combination;
    ARVExpansionContainer combination_expand;
    AntiviralSet::Combinator::generate_power_set(power_set_combination);
    AntiviralSet::Combinator::generate_cartesian_product(power_set_combination, cartesian_product_combination);
    expand_combination_to_nucleotide(cartesian_product_combination, combination_expand);
    AntiviralSet::Combinator::generate_triplets_cartesian_product(combination_expand, combination);
}

// -----------------------
// Private methods section
// -----------------------

void AntiviralSet::Combinator::update_arv_information(const biopp::PseudoNucSequence& sequence,
        ArvsSet::iterator& it_av) const
{
    ResisBasedAntiviral::ResistancesGroupContainer::iterator it_groups;
    ResisBasedAntiviral::ResistancesGroupContainer& current_group = (*it_av)->resGroups;
    for (it_groups = current_group.begin(); it_groups != current_group.end(); ++it_groups)
        // Processing Groups
        update_group_information(sequence, it_groups);
}


void AntiviralSet::Combinator::make_power_set(const ArvsSet::const_iterator& it_av,
        ResisBasedAntiviral::CombinationContainer& combination_container_generated) const
{
    ResisBasedAntiviral::Combination current_combination;
    ResisBasedAntiviral::ResistancesGroupContainer::const_iterator it_groups;
    ResisBasedAntiviral::ResistancesGroupContainer current_group = (*it_av)->resGroups;
    for (it_groups = current_group.begin(); it_groups != current_group.end(); ++it_groups)
        // Calculate the power set for the current group
        it_groups->generate_power_set(current_combination);

    // The information for the current antiviral is added to result
    mili::insert_into(combination_container_generated, current_combination);
}


void AntiviralSet::Combinator::process_combinations(const ResisBasedAntiviral::Combination::const_iterator& it_comb,
        const biopp::PseudoNucSequence& sequence,
        AminoacidExpansion& aminoacid_expansion) const
{
    ResisBasedAntiviral::ResistancePositionContainer::const_iterator it_resistances;
    ResisBasedAntiviral::ResistancePositionContainer current_resistance = (*it_comb);

    for (it_resistances = current_resistance.begin(); it_resistances != current_resistance.end(); ++it_resistances)
    {
        // Expansion to triplets
        generate_pos_triplet_list(it_resistances, sequence, aminoacid_expansion);
    }
}


void AntiviralSet::Combinator::process_combinations_container(const ResisBasedAntiviral::CombinationContainer::const_iterator& it_comb_cont,
        const biopp::PseudoNucSequence& sequence,
        ARVExpansion& arv_expansion) const
{
    ResisBasedAntiviral::Combination::const_iterator it_comb;
    ResisBasedAntiviral::Combination current_combination = (*it_comb_cont);
    for (it_comb = current_combination.begin(); it_comb != current_combination.end(); ++it_comb)
    {
        AminoacidExpansion aminoacid_expanded;
        process_combinations(it_comb, sequence, aminoacid_expanded);
        mili::insert_into(arv_expansion, aminoacid_expanded);
    }
}


void AntiviralSet::Combinator::update_group_information(const biopp::PseudoNucSequence& sequence,
        ResisBasedAntiviral::ResistancesGroupContainer::iterator& it_groups) const
{
    ResisBasedAntiviral::ResistancePositionContainer& current_resistance = it_groups->resistances;
    ResisBasedAntiviral::ResistancePositionContainer::iterator it_resistances = current_resistance.begin();
    while (it_resistances != current_resistance.end())
    {
        // Processing Resistances
        TripletVal triplet_min_dist;
        it_resistances->min_dist_to_resistance(sequence, triplet_min_dist);

        if (triplet_min_dist.second == 0.0)
        {
            // Current resistance matches with the sequence

            // Applies rule 1.1
            it_groups->minResistanceWeight -= it_resistances->weight;

            // Applies rule 1.2
            ResisBasedAntiviral::ResistancePositionContainer::iterator itTmp = it_resistances; //
            ++itTmp;                                                                            // Check this!
            it_groups->resistances.remove(*it_resistances);                                     //
            it_resistances = itTmp;                                                             //

        }
        else
            ++it_resistances;
    }
}


void AntiviralSet::Combinator::split_antivirals_x(const ResisBasedAntiviral::CombinationContainer& aux_antivirals_x,
        ResisBasedAntiviral::CombinationContainer& antivirals_x) const
{
    ResisBasedAntiviral::CombinationContainer::const_iterator it_av;
    for (it_av = aux_antivirals_x.begin(); it_av != aux_antivirals_x.end(); ++it_av)
    {
        ResisBasedAntiviral::Combination current_combination = (*it_av);
        ResisBasedAntiviral::Combination::const_iterator it_comb;
        ResisBasedAntiviral::Combination new_combination;
        for (it_comb = current_combination.begin(); it_comb != current_combination.end(); ++it_comb)
        {
            ResisBasedAntiviral::ResistancePositionContainer current_res = (*it_comb);
            if (current_res.size() > 1)
            {
                // Composed combination, so it's splited
                ResisBasedAntiviral::ResistancePositionContainer::const_iterator it_res;
                for (it_res = current_res.begin(); it_res != current_res.end(); ++it_res)
                {
                    ResisBasedAntiviral::ResistancePositionContainer new_resistances_cont;
                    mili::insert_into(new_resistances_cont, (*it_res));
                    mili::insert_into(new_combination, new_resistances_cont);
                }
            }
            else
                mili::insert_into(new_combination, current_res);
        }
        mili::insert_into(antivirals_x, new_combination);
    }
}


void AntiviralSet::Combinator::generate_pos_triplet_list(const ResisBasedAntiviral::ResistancePositionContainer::const_iterator& it_resistance,
        const biopp::PseudoNucSequence& sequence,
        AminoacidExpansion& aminoacid_expansion) const
{
    TripletList triplets;
    it_resistance->expand_aminoacid(sequence, triplets);
    TripletList::const_iterator it_triplets;
    for (it_triplets = triplets.begin(); it_triplets != triplets.end(); ++it_triplets)
    {
        ExpandedResistance current_expanded_resistance(it_resistance->pos, (*it_triplets));
        mili::insert_into(aminoacid_expansion, current_expanded_resistance);
    }
}


void AntiviralSet::Combinator::expand_combination_to_nucleotide(const ResisBasedAntiviral::CombinationContainer& antivirals,
        ARVExpansionContainer& antivirals_expanded) const
{
    ResisBasedAntiviral::CombinationContainer::const_iterator it_comb_cont;
    ResisBasedAntiviral::Combination::const_iterator it_comb;
    ResisBasedAntiviral::ResistancePositionContainer::const_iterator it_resistance;
    for (it_comb_cont = antivirals.begin(); it_comb_cont != antivirals.end(); ++it_comb_cont)
    {
        ARVExpansion current_expansion;
        for (it_comb = (*it_comb_cont).begin(); it_comb != (*it_comb_cont).end(); ++it_comb)
        {
            AminoacidExpansion amin_expansion;
            for (it_resistance = (*it_comb).begin(); it_resistance != (*it_comb).end(); ++it_resistance)
            {
                expand_aminoacids_to_triplets(it_resistance, amin_expansion);
            }
            mili::insert_into(current_expansion, amin_expansion);
        }
        mili::insert_into(antivirals_expanded, current_expansion);
    }

}


void AntiviralSet::Combinator::expand_aminoacids_to_triplets(const ResisBasedAntiviral::ResistancePositionContainer::const_iterator& it_resistance,
        AminoacidExpansion& amin_expansion) const
{
    biopp::TripletsIterator it_triplets;
    for (size_t i = 0; i < it_resistance->aminoacids.size(); ++i)
    {
        for (it_triplets = biopp::GeneticCode::triplets(it_resistance->aminoacids[i]); !it_triplets.end(); ++it_triplets)
        {
            ExpandedResistance resitence_exp(it_resistance->pos, *it_triplets);
            mili::insert_into(amin_expansion, resitence_exp);
        }
    }
}
}